package org.xtremeobjects.domain.states;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xtremeobjects.util.collections.CollectionSelector;




/**
 * @author    Juan
 */
public class State {

	static public final Event ENTRY = new Event(null,"Entry");
	static public final Event EXIT = new Event(null,"Exit");
	static public final Event DO = new Event(null,"Do");
	
	public State(StateMachine stateMachine, String name) {
		this(stateMachine, name, null);
	}

	public State(StateMachine stateMachine, String name, State superState) {
		this.stateMachine = stateMachine;
		this.superState = superState;
		this.name = name;
	}

	/**
	 * @return
	 * @uml.property  name="name"
	 */
	public String getId() {
		return this.name;
	}

	public Transition to(State to) {
		return createTransition(this, to);
	}

	public Transition from(State from) {
		return createTransition(from, this);
	}
	
	public State state(String name) {
		State state = findStateByName(name);
		if(state==null)
			state = createState(name);
		return state;
	}
	

	private State createState(String name) {
		State state = new State( stateMachine, name, this );
		addState(state);
		return state;
	}

	private void addState(State state) {
		substates.add(state);
	}

	private State findStateByName(String subStateName) {
		for (State state : getStates()) {
			if(state.getId().equals(subStateName))
				return state;
		}
		return null;
	}

	public Event event(String eventName) {
		Event event = null;
		if(eventName.equals(EXIT.getId()))
			event = EXIT;
		else if(eventName.equals(ENTRY.getId()))
			event = ENTRY;
		else if(eventName.equals(DO.getId()))
			event = DO;
		else {
			event = stateMachine.findEventByName(eventName);
			if(event==null)
				event = stateMachine.createEvent(eventName);
		}
		return event;
	}

	public void addActionForEntry(String string) {
		addActionForEvent(ENTRY, string);
	}

	public void addActionForExit(String string) {
		addActionForEvent(EXIT, string);
	}

	public void addActionForDo(String string) {
		addActionForEvent(DO, string);
	}
	
	public void addActionForEvent(String eventName, String actionName) {
		Event anEvent = stateMachine.findEventByName(eventName);
		if(anEvent==null)
			anEvent = stateMachine.createEvent(eventName);
		addActionForEvent(anEvent, actionName);
	}

	private void addActionForEvent(Event event, String action) {
		List<Action> actionList = getActionsForEvent(event);
		actionList.add(new Action(action));
	}

	public List<Action> getActionsForEvent(Event event) {
		List<Action> actionList = getActions().get(event);
		if(actionList==null) {
			actionList = new ArrayList<Action>();
			getActions().put(event, actionList);
		}
		return actionList;
	}

	private Transition createTransition(State from, State to) {
		return stateMachine.createTransition(from,to);
	}

	private List<Transition> findTransition(State from, State to) {
		return stateMachine.findTransition(from, to);
	}

	private Map<Event, List<Action>> getActions() {
		if(actions==null)
			actions = new HashMap<Event, List<Action>>();
		return actions;
	}

	public List<State> getStates() {
		if(substates==null)
			substates = new ArrayList<State>();
		return substates;
	}
	
	/**
	 * @return
	 * @uml.property  name="superState"
	 */
	public State getSuperState() {
		return superState;
	}
	
	/**
	 * @uml.property  name="name"
	 */
	private String name;
	private StateMachine stateMachine;
	private State superState;
	/**
	 * @uml.property  name="actions"
	 */
	private Map<Event, List<Action>> actions;
	private List<State> substates;
	


	/**
	 * Returns the next state from this state.
	 * This only works when a single transition to other state is present,
	 * if more than one or no transition is found it throws an exception.
	 * @return next state
	 */
	public State getNextState() {
		List<Transition> transitions = getTransitions();
		if(transitions.size()!=1)
			throw new RuntimeException("There is no next state or it is not unique for state " + getId());
		return transitions.get(0).getTo();
	}

	public List<Transition> getTransitionsForEvent(Event event) {
		
        CollectionSelector stateSelector = new CollectionSelector()
        {
            public boolean selectBlock(Object listElement, Object keyValue)
            {
                return ((Transition)listElement).getEvent().equals(keyValue);
            }
        };
        List<Transition> transitions = (List<Transition>)stateSelector.select(getTransitions(), event);

        //TODO: check if this works for multiple levels of super states 
        //Collect transitions from superstate
        if(getSuperState()!=null)
        	transitions.addAll(stateSelector.select(getSuperState().getTransitions(), event));
        
        return transitions;
	}

	/**
	 * Returns the transitions from this state.
	 * @return
	 */
	public List<Transition> getTransitions() {
		return stateMachine.getTransitionsFrom(this);
	}
}
